--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
meshchatx/src/backend/message_handler.py 4bd787ccd70a459a02eececca3f942f0e64ec4ce (4bd787cc) Text, 9.72 KB
T8b949e# SPDX-License-Identifier: 0BSD
Tff7b72from T7ee787.T7ee787database Tff7b72import Te6edf3Database
Tff7b72def Td2a8ff_strip_utf16_surrogatesTb4b4b4(Te6edf3textTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3text Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72return Ta5d6ff"Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3c Tff7b72for Te6edf3c Tff7b72in Tffa657strTb4b4b4(Te6edf3textTb4b4b4) Tff7b72if Tff7b72not Tb4b4b4(T79c0ff0xD800 Tff7b72<Tff7b72= Tffa657ordTb4b4b4(Te6edf3cTb4b4b4) Tff7b72<Tff7b72= T79c0ff0xDFFFTb4b4b4)Tb4b4b4)
Tff7b72class T56d364MessageHandlerTb4b4b4:
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3dbTb4b4b4: Te6edf3DatabaseTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffdb Tff7b72= Te6edf3db
T8b949e# Columns needed by convert_db_lxmf_message_to_dict / ConversationViewer.
T8b949e# Prefer fields_meta (no attachment bytes). Fall back to small fields only.
Te6edf3_CONVERSATION_MESSAGE_COLUMNS Tff7b72= Ta5d6ff"""
Ta5d6ff id, hash, source_hash, destination_hash, peer_hash, state, progress,
Ta5d6ff is_incoming, method, delivery_attempts, next_delivery_attempt_at,
Ta5d6ff title, content, timestamp, rssi, snr, quality, is_spam, reply_to_hash,
Ta5d6ff attachments_stripped, path_hops_at_send, path_interface_at_send,
Ta5d6ff path_finding_measure, path_row_hash_hex, created_at, updated_at,
Ta5d6ff COALESCE(has_image, 0) as has_image,
Ta5d6ff COALESCE(has_audio, 0) as has_audio,
Ta5d6ff COALESCE(has_files, 0) as has_files,
Ta5d6ff COALESCE(has_reaction, 0) as has_reaction,
Ta5d6ff COALESCE(has_telemetry, 0) as has_telemetry,
Ta5d6ff CASE
Ta5d6ff WHEN fields_meta IS NOT NULL AND fields_meta != Ta5d6ff'Ta5d6ff'Ta5d6ff THEN fields_meta
Ta5d6ff WHEN length(COALESCE(fields, Ta5d6ff'Ta5d6ff'Ta5d6ff)) <= 16384 THEN fields
Ta5d6ff ELSE NULL
Ta5d6ff END as fields
Ta5d6ff Ta5d6ff"""
T8b949e# Default and hard cap when callers omit or overshoot limit.
Te6edf3DEFAULT_CONVERSATIONS_LIMIT Tff7b72= T79c0ff500
Te6edf3MAX_CONVERSATIONS_LIMIT Tff7b72= T79c0ff2000
Tff7b72def Td2a8ffget_conversation_messagesTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3local_hashTb4b4b4,
Te6edf3destination_hashTb4b4b4,
Te6edf3limitTff7b72=T79c0ff100Tb4b4b4,
Te6edf3offsetTff7b72=T79c0ff0Tb4b4b4,
Te6edf3after_idTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3before_idTff7b72=Tff7b72NoneTb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3query Tff7b72= Ta5d6fffTa5d6ff"""
Ta5d6ff SELECT Tffd700{Tff7b72selfTff7b72.Td2a8ff_CONVERSATION_MESSAGE_COLUMNSTffd700}
Ta5d6ff FROM lxmf_messages
Ta5d6ff WHERE peer_hash = ?
Ta5d6ff Ta5d6ff"""
Te6edf3params Tff7b72= Tb4b4b4[Te6edf3destination_hashTb4b4b4]
Tff7b72if Te6edf3after_idTb4b4b4:
Te6edf3query Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff AND id > ?Ta5d6ff"
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3after_idTb4b4b4)
Tff7b72if Te6edf3before_idTb4b4b4:
Te6edf3query Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff AND id < ?Ta5d6ff"
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3before_idTb4b4b4)
Te6edf3query Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff ORDER BY id DESC LIMIT ? OFFSET ?Ta5d6ff"
Te6edf3paramsTff7b72.Td2a8ffextendTb4b4b4(Tb4b4b4[Te6edf3limitTb4b4b4, Te6edf3offsetTb4b4b4]Tb4b4b4)
Tff7b72return Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3paramsTb4b4b4)
Tff7b72def Td2a8ffdelete_conversationTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3local_hashTb4b4b4, Te6edf3destination_hashTb4b4b4)Tb4b4b4:
Te6edf3query Tff7b72= Ta5d6ff"Ta5d6ffDELETE FROM lxmf_messages WHERE peer_hash = ?Ta5d6ff"
Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(Te6edf3queryTb4b4b4, Tb4b4b4[Te6edf3destination_hashTb4b4b4]Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_read_state WHERE destination_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4[Te6edf3destination_hashTb4b4b4]Tb4b4b4,
Tb4b4b4)
T8b949e# Also clean up folder mapping
Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_folders WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4[Te6edf3destination_hashTb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffproviderTff7b72.Td2a8ffexecuteTb4b4b4(
Ta5d6ff"Ta5d6ffDELETE FROM lxmf_conversation_pins WHERE peer_hash = ?Ta5d6ff"Tb4b4b4,
Tb4b4b4[Te6edf3destination_hashTb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffmessagesTff7b72.Td2a8ffdelete_conversation_summaryTb4b4b4(Te6edf3destination_hashTb4b4b4)
Tff7b72def Td2a8ffsearch_messagesTb4b4b4(Tff7b72selfTb4b4b4, Te6edf3local_hashTb4b4b4, Te6edf3search_termTb4b4b4, Te6edf3limitTff7b72=T79c0ff500Tb4b4b4)Tb4b4b4:
Te6edf3search_term Tff7b72= Te6edf3_strip_utf16_surrogatesTb4b4b4(Te6edf3search_termTb4b4b4) Tff7b72or Ta5d6ff"Ta5d6ff"
Te6edf3like_term Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ff%Tffd700{Te6edf3search_termTffd700}Ta5d6ff%Ta5d6ff"
Te6edf3query Tff7b72= Ta5d6ff"""
Ta5d6ff SELECT peer_hash, MAX(timestamp) as max_ts
Ta5d6ff FROM lxmf_messages
Ta5d6ff WHERE title LIKE ? OR content LIKE ? OR peer_hash LIKE ?
Ta5d6ff GROUP BY peer_hash
Ta5d6ff ORDER BY max_ts DESC
Ta5d6ff LIMIT ?
Ta5d6ff Ta5d6ff"""
Te6edf3params Tff7b72= Tb4b4b4[Te6edf3like_termTb4b4b4, Te6edf3like_termTb4b4b4, Te6edf3like_termTb4b4b4, Te6edf3limitTb4b4b4]
Tff7b72return Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3paramsTb4b4b4)
T8b949e# Keep conversation-list payloads small. Full fields often embeds
T8b949e# multi-MB base64 attachments and must never be loaded into the list API.
T8b949e# Prefer persisted has_* columns (schema v51+). Fall back to instr only
T8b949e# when flags were never backfilled (NULL) on filter paths.
Te6edf3_CONVERSATION_CONTENT_PREVIEW_CHARS Tff7b72= T79c0ff240
Te6edf3_FIELDS_HAS_IMAGE_SQL Tff7b72= Ta5d6ff"Ta5d6ffCOALESCE(s.has_image, 0)Ta5d6ff"
Te6edf3_FIELDS_HAS_AUDIO_SQL Tff7b72= Ta5d6ff"Ta5d6ffCOALESCE(s.has_audio, 0)Ta5d6ff"
Te6edf3_FIELDS_HAS_FILES_SQL Tff7b72= Ta5d6ff"Ta5d6ffCOALESCE(s.has_files, 0)Ta5d6ff"
Te6edf3_FIELDS_HAS_REACTION_SQL Tff7b72= Ta5d6ff"Ta5d6ffCOALESCE(s.has_reaction, 0)Ta5d6ff"
Te6edf3_FIELDS_HAS_TELEMETRY_SQL Tff7b72= Ta5d6ff"Ta5d6ffCOALESCE(s.has_telemetry, 0)Ta5d6ff"
Te6edf3_FIELDS_HAS_ATTACHMENTS_SQL Tff7b72= Tb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ff(Tffd700{Te6edf3_FIELDS_HAS_IMAGE_SQLTffd700}Ta5d6ff = 1 OR Tffd700{Te6edf3_FIELDS_HAS_AUDIO_SQLTffd700}Ta5d6ff = 1 Ta5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffOR Tffd700{Te6edf3_FIELDS_HAS_FILES_SQLTffd700}Ta5d6ff = 1)Ta5d6ff"
Tb4b4b4)
T8b949e# Filter path: prefer summary has_* so filter_has_attachments stays cheap.
Te6edf3_FILTER_HAS_ATTACHMENTS_SQL Tff7b72= Tb4b4b4(
Ta5d6ff"Ta5d6ff(COALESCE(s.has_image, 0) = 1 OR COALESCE(s.has_audio, 0) = 1 Ta5d6ff"
Ta5d6ff"Ta5d6ffOR COALESCE(s.has_files, 0) = 1)Ta5d6ff"
Tb4b4b4)
Tf0883e@classmethod
Tff7b72def Td2a8ffclamp_conversations_limitTb4b4b4(Tff7b72clsTb4b4b4, Te6edf3limitTb4b4b4)Tb4b4b4:
T8b949e"""Normalize list limit. None becomes the default. Cap at MAX."""
Tff7b72if Te6edf3limit Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Tff7b72clsTff7b72.Td2a8ffDEFAULT_CONVERSATIONS_LIMIT
Tff7b72tryTb4b4b4:
Te6edf3value Tff7b72= Tffa657intTb4b4b4(Te6edf3limitTb4b4b4)
Tff7b72except Tb4b4b4(Tf85149TypeErrorTb4b4b4, Tf85149ValueErrorTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72clsTff7b72.Td2a8ffDEFAULT_CONVERSATIONS_LIMIT
Tff7b72if Te6edf3value Tff7b72< T79c0ff0Tb4b4b4:
Tff7b72return T79c0ff0
Tff7b72if Te6edf3value Tff7b72> Tff7b72clsTff7b72.Td2a8ffMAX_CONVERSATIONS_LIMITTb4b4b4:
Tff7b72return Tff7b72clsTff7b72.Td2a8ffMAX_CONVERSATIONS_LIMIT
Tff7b72return Te6edf3value
Tff7b72def Td2a8ffget_conversationsTb4b4b4(
Tff7b72selfTb4b4b4,
Te6edf3local_hashTb4b4b4,
Te6edf3searchTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3filter_unreadTff7b72=Tff7b72FalseTb4b4b4,
Te6edf3filter_failedTff7b72=Tff7b72FalseTb4b4b4,
Te6edf3filter_has_attachmentsTff7b72=Tff7b72FalseTb4b4b4,
Te6edf3folder_idTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3limitTff7b72=T79c0ff500Tb4b4b4,
Te6edf3offsetTff7b72=T79c0ff0Tb4b4b4,
Tb4b4b4)Tb4b4b4:
Te6edf3limit Tff7b72= Tff7b72selfTff7b72.Td2a8ffclamp_conversations_limitTb4b4b4(Te6edf3limitTb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3offset Tff7b72= Tffa657maxTb4b4b4(T79c0ff0Tb4b4b4, Tffa657intTb4b4b4(Te6edf3offset Tff7b72or T79c0ff0Tb4b4b4)Tb4b4b4)
Tff7b72except Tb4b4b4(Tf85149TypeErrorTb4b4b4, Tf85149ValueErrorTb4b4b4)Tb4b4b4:
Te6edf3offset Tff7b72= T79c0ff0
Te6edf3query Tff7b72= Ta5d6fffTa5d6ff"""
Ta5d6ff SELECT
Ta5d6ff s.latest_message_id as id,
Ta5d6ff s.latest_message_hash as hash,
Ta5d6ff s.source_hash, s.destination_hash,
Ta5d6ff s.peer_hash, s.state, s.progress, s.is_incoming,
Ta5d6ff s.title,
Ta5d6ff s.content_preview as content,
Ta5d6ff s.timestamp,
Ta5d6ff s.is_spam, s.reply_to_hash,
Ta5d6ff s.created_at, s.updated_at,
Ta5d6ff (Tffd700{Tff7b72selfTff7b72.Td2a8ff_FIELDS_HAS_IMAGE_SQLTffd700}Ta5d6ff) as has_image,
Ta5d6ff (Tffd700{Tff7b72selfTff7b72.Td2a8ff_FIELDS_HAS_AUDIO_SQLTffd700}Ta5d6ff) as has_audio,
Ta5d6ff (Tffd700{Tff7b72selfTff7b72.Td2a8ff_FIELDS_HAS_FILES_SQLTffd700}Ta5d6ff) as has_files,
Ta5d6ff (Tffd700{Tff7b72selfTff7b72.Td2a8ff_FIELDS_HAS_REACTION_SQLTffd700}Ta5d6ff) as has_reaction,
Ta5d6ff (Tffd700{Tff7b72selfTff7b72.Td2a8ff_FIELDS_HAS_TELEMETRY_SQLTffd700}Ta5d6ff) as has_telemetry,
Ta5d6ff CASE WHEN Tffd700{Tff7b72selfTff7b72.Td2a8ff_FIELDS_HAS_ATTACHMENTS_SQLTffd700}Ta5d6ff THEN 1 ELSE 0 END as has_attachments,
Ta5d6ff a.app_data as peer_app_data,
Ta5d6ff c.display_name as custom_display_name,
Ta5d6ff CASE
Ta5d6ff WHEN con.custom_image IS NOT NULL AND con.custom_image != Ta5d6ff'Ta5d6ff'
Ta5d6ff THEN 1 ELSE 0
Ta5d6ff END as has_contact_image,
Ta5d6ff con.name as contact_name,
Ta5d6ff i.icon_name, i.foreground_colour, i.background_colour,
Ta5d6ff r.last_read_at,
Ta5d6ff f.id as folder_id,
Ta5d6ff fn.name as folder_name,
Ta5d6ff COALESCE(s.failed_count, 0) as failed_count,
Ta5d6ff CASE WHEN con.id IS NOT NULL THEN 1 ELSE 0 END as is_contact
Ta5d6ff FROM lxmf_conversation_summaries s
Ta5d6ff LEFT JOIN announces a ON a.destination_hash = s.peer_hash
Ta5d6ff LEFT JOIN custom_destination_display_names c ON c.destination_hash = s.peer_hash
Ta5d6ff LEFT JOIN contacts con ON (
Ta5d6ff con.remote_identity_hash = s.peer_hash OR
Ta5d6ff con.lxmf_address = s.peer_hash OR
Ta5d6ff con.lxst_address = s.peer_hash
Ta5d6ff )
Ta5d6ff LEFT JOIN lxmf_user_icons i ON i.destination_hash = s.peer_hash
Ta5d6ff LEFT JOIN lxmf_conversation_read_state r ON r.destination_hash = s.peer_hash
Ta5d6ff LEFT JOIN lxmf_conversation_folders f ON f.peer_hash = s.peer_hash
Ta5d6ff LEFT JOIN lxmf_folders fn ON fn.id = f.folder_id
Ta5d6ff Ta5d6ff"""
Te6edf3params Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3where_clauses Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72if Te6edf3folder_id Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Tff7b72if Te6edf3folder_id Tff7b72in Tb4b4b4{T79c0ff0Tb4b4b4, Ta5d6ff"Ta5d6ff0Ta5d6ff"Tb4b4b4}Tb4b4b4:
T8b949e# Special case: no folder (Uncategorized)
Te6edf3where_clausesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6fff.folder_id IS NULLTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3where_clausesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6fff.folder_id = ?Ta5d6ff"Tb4b4b4)
Te6edf3paramsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3folder_idTb4b4b4)
Tff7b72if Te6edf3filter_unreadTb4b4b4:
Te6edf3where_clausesTff7b72.Td2a8ffappendTb4b4b4(
Ta5d6ff"Ta5d6ff(s.is_incoming = 1 AND (r.last_read_at IS NULL OR s.timestamp > strftime(Ta5d6ff'Tffd700%sTa5d6ff'Ta5d6ff, r.last_read_at)))Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3filter_failedTb4b4b4:
Te6edf3where_clausesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffs.state = Ta5d6ff'Ta5d6fffailedTa5d6ff'Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3filter_has_attachmentsTb4b4b4:
Te6edf3where_clausesTff7b72.Td2a8ffappendTb4b4b4(Tff7b72selfTff7b72.Td2a8ff_FILTER_HAS_ATTACHMENTS_SQLTb4b4b4)
Tff7b72if Te6edf3searchTb4b4b4:
Te6edf3search Tff7b72= Te6edf3_strip_utf16_surrogatesTb4b4b4(Te6edf3searchTb4b4b4) Tff7b72or Ta5d6ff"Ta5d6ff"
Tff7b72if Te6edf3searchTb4b4b4:
Te6edf3like_term Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ff%Tffd700{Te6edf3searchTffd700}Ta5d6ff%Ta5d6ff"
T8b949e# Search latest summary fields or any historical message for the peer
Te6edf3where_clausesTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"""
Ta5d6ff (s.title LIKE ? OR s.content_preview LIKE ? OR s.peer_hash LIKE ?
Ta5d6ff OR c.display_name LIKE ? OR con.name LIKE ?
Ta5d6ff OR s.peer_hash IN (
Ta5d6ff SELECT peer_hash FROM lxmf_messages
Ta5d6ff WHERE title LIKE ? OR content LIKE ?
Ta5d6ff ))
Ta5d6ff Ta5d6ff"""Tb4b4b4)
Te6edf3paramsTff7b72.Td2a8ffextendTb4b4b4(
Tb4b4b4[
Te6edf3like_termTb4b4b4,
Te6edf3like_termTb4b4b4,
Te6edf3like_termTb4b4b4,
Te6edf3like_termTb4b4b4,
Te6edf3like_termTb4b4b4,
Te6edf3like_termTb4b4b4,
Te6edf3like_termTb4b4b4,
Tb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3where_clausesTb4b4b4:
Te6edf3query Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff WHERE Ta5d6ff" Tff7b72+ Ta5d6ff"Ta5d6ff AND Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3where_clausesTb4b4b4)
Te6edf3query Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff GROUP BY s.peer_hash ORDER BY s.latest_message_id DESCTa5d6ff"
Te6edf3query Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff LIMIT ? OFFSET ?Ta5d6ff"
Te6edf3paramsTff7b72.Td2a8ffextendTb4b4b4(Tb4b4b4[Te6edf3limitTb4b4b4, Te6edf3offsetTb4b4b4]Tb4b4b4)
Tff7b72return Tff7b72selfTff7b72.Td2a8ffdbTff7b72.Td2a8ffproviderTff7b72.Td2a8fffetchallTb4b4b4(Te6edf3queryTb4b4b4, Te6edf3paramsTb4b4b4)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────